import { Asset, AssetManager, ImageAsset, Prefab, SpriteFrame, Texture2D, assetManager, dragonBones } from 'cc';

class ResManager {
  private static instance: ResManager;
  public static get Ins() {
    if (!this.instance) this.instance = new ResManager();
    return this.instance;
  }
  private constructor() {}
  public static clear() {
    this.instance = null;
  }

  private total: number = 0;
  private finish = 0;
  private prefabs: { [key: string]: any } = {};
  public constResource: { [url: string]: boolean } = {};

  async init(callback: (number: number, count: number, resName: string) => void) {
    this.total = 0;
    this.finish = 0;
    let otherResources = [];
    let remoteResources = [];
    this.total = otherResources.length + remoteResources.length;

    this.total++;
    this.getBundle('FindRoad').then((bundle) => {
      this.loadAll(bundle, callback);
      this.finish++;
      callback(this.finish, this.total, 'FindRoad');
    });

    // this.total++
    // this.loadBin(callback)
  }

  loadAll(bundle: AssetManager.Bundle, callback?: Function) {
    let map = bundle['_config']['paths']['_map'];
    this.total += bundle['_config']['paths']['_count'];
    for (const url in map) {
      if (Object.prototype.hasOwnProperty.call(map, url)) {
        if (url.includes('image') && !url.includes('spriteFrame')) {
          this.finish++;
          callback(this.finish, this.total);
        } else {
          this.bundleLoad(bundle, url, callback);
        }
      }
    }
  }

  private bundleLoad<T extends Asset>(bundle: AssetManager.Bundle, path: string, onComplete?: Function) {
    return new Promise<T>(async (resolve, reject) => {
      bundle.load(path, Asset, (error, asset: T) => {
        if (error) {
          console.error('ResManager.bundleLoad error:', path, error);
          reject(null);
        } else {
          resolve(asset);
          if (asset instanceof Prefab) {
            this.prefabs[path] = asset;
          }
          if (onComplete) {
            this.finish++;
            onComplete(this.finish, this.total, path);
          }
        }
      });
    });
  }

  private loadedBundle: string[] = [];
  async getBundle(name: string) {
    return new Promise<AssetManager.Bundle>((resolve, reject) => {
      let startTime = Date.now();
      assetManager.loadBundle(name, (err, bundle: AssetManager.Bundle) => {
        if (!err) {
          if (!this.loadedBundle.includes(name)) {
            this.loadedBundle.push(name);
            console.log('第一次加载bundle ' + name + '花费了' + (Date.now() - startTime));
          }
          resolve(bundle);
        } else {
          reject(null);
        }
      });
    });
  }

  get<T extends Asset>(url: string, T: typeof Asset = Asset): T {
    let arr = url.split(/\/(.+)/);
    let bundle = assetManager.getBundle(arr[0]),
      path: string = arr[1];
    if (!bundle) return null;
    return bundle.get(path, T) as T;
  }

  async load<T extends Asset>(url: string, T: typeof Asset = Asset, onComplete?: Function) {
    return new Promise<T>(async (resolve, reject) => {
      let path: string = url.split(/\/(.+)/)[1],
        bundle = await this.getBundle(url.split(/\/(.+)/)[0]);

      // 3.x特性
      if (T === SpriteFrame) path += '/spriteFrame';

      bundle.load(path, T, (err, asset: T) => {
        if (err) {
          console.error('ResManager.load error:', path, err);
          reject(null);
          return;
        }
        resolve(asset);
        if (onComplete) {
          onComplete(++this.finish, this.total);
          return;
        }
        this.addRef(asset, url);
      });
    });
  }

  addRef(asset, url: string) {
    asset.addRef();
    if (!this.resMap.get(url)) {
      this.resMap.set(url, asset);
    }
  }

  loadRemote<T extends Asset>(url: string, type: typeof Asset = Asset, onComplete?: Function) {
    return new Promise<T>(async (resolve, reject) => {
      assetManager.loadRemote<T>(url, (err, asset: T) => {
        if (err) {
          console.error('ResManager.loadRemote error:', err);
          reject(null);
          return;
        }
        if (type === SpriteFrame && asset instanceof ImageAsset) {
          const spriteFrame = new SpriteFrame();
          const texture = new Texture2D();
          texture.image = asset;
          spriteFrame.texture = texture;
          resolve(spriteFrame as any);
        } else {
          resolve(asset);
        }
        this.addRef(asset, url);
        if (onComplete) {
          onComplete(++this.finish, this.total);
        }
      });
    });
  }

  private resMap: Map<string, Asset> = new Map();
  release(url: string) {
    let asset = this.resMap.get(url);
    if (this.constResource[url]) return;
    if (asset && asset.refCount > 0) {
      asset.decRef();
      if (asset.refCount == 0) {
        this.resMap.delete(url);
      }
    }
  }

  /**
   * 传入编辑器assets里的路径,返回构建后文件所在的路劲,需要带完整的后缀名
   * 例如获得bin文件路径，传入"resource/bin/configs.bin"
   * 例如获得视频文件路径，传入"other/video/video1.mp4"
   */
  public async getAfterBuildPath(path: string): Promise<string> {
    let arr = path.split(/\/(.+)/);
    let bundleName = arr[0];
    let filePath = arr[1];
    let pointIndex = filePath.lastIndexOf('.');
    let nativeExt = filePath.substring(pointIndex, filePath.length);
    filePath = filePath.substring(0, pointIndex);

    let bundle = await this.getBundle(bundleName);
    let uuid = bundle.getInfoWithPath('/' + filePath).uuid;
    path = assetManager.utils.getUrlWithUuid(uuid, { isNative: true, nativeExt });
    return path;
  }

  async loadDir(bundleName: string, path: string, onComplete?: Function, T: typeof Asset = Asset) {
    let bundle = await this.getBundle(bundleName);
    return new Promise<Asset[]>(async (resolve, reject) => {
      bundle.loadDir(path, T, (er: Error, res) => {
        if (er) {
          console.error('loadres error path:' + path + '  error:' + er);
          reject(null);
          return;
        }
        resolve(res);
        if (onComplete) {
          onComplete(res);
        }
      });
    });
  }

  async loadDragonBonesFromBundle(dragonBonesNode: dragonBones.ArmatureDisplay, bundleName: string, dir: string, call = null) {
    await this.loadDir(bundleName, dir, (assets: Asset[]) => {
      assets.forEach((asset) => {
        if (asset instanceof dragonBones.DragonBonesAsset) {
          dragonBonesNode.dragonAsset = asset;
        }
        if (asset instanceof dragonBones.DragonBonesAtlasAsset) {
          dragonBonesNode.dragonAtlasAsset = asset;
        }
      });
      dragonBonesNode.armatureName = 'armatureName';
      call && call();
    });
    return dragonBonesNode;
  }
}

export default ResManager;
